package iot.wlink.network.devices.manager;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import iot.wlink.bean.dao.DeviceSessionDo;
import iot.wlink.mapper.DeviceMapper;
import iot.wlink.mapper.DeviceShareMapper;
import iot.wlink.network.codec.script.ScriptCodec;
import iot.wlink.network.devices.model.DeviceModel;
import iot.wlink.network.devices.session.DeviceSession;
import iot.wlink.network.devices.session.SessionAndShadow;
import iot.wlink.network.devices.shadows.DeviceShadow;
import iot.wlink.network.payload.down.DownPayload;
import iot.wlink.network.payload.down.ServerErrorAck;
import iot.wlink.network.payload.up.PointValue;
import iot.wlink.network.payload.up.UpPayload;
import iot.wlink.utils.BeanUtil;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : willian fu
 * @date : 2021/8/20
 * 抽象设备管理器，缓存设备信息
 * 为了加快数据访问速度和方便做设备统一管理
 */
public abstract class AbstractDeviceManager implements DeviceManager, SessionManager, ShadowManager{

    //设备信息缓存
    protected static final Map<String, SessionAndShadow> storage = new ConcurrentHashMap<>();

    //脚本解析器缓存
    protected static final Map<Long, ScriptCodec> codecs = new ConcurrentHashMap<>();

    @Override
    public boolean isOnline(String uid) {
        SessionAndShadow sessionAndShadow = storage.get(uid);
        return ObjectUtil.isNotNull(sessionAndShadow) && sessionAndShadow.getIsOnline();
    }

    @Override
    public void kickOut(String uid, ServerErrorAck error) {
        deviceOffline(uid);
        sendData(uid, error);
        disconnect(uid);
    }

    @Override
    public void deviceOnline(String uid) {
        SessionAndShadow sessionAndShadow = storage.get(uid);
        if (ObjectUtil.isNull(sessionAndShadow)){
            storage.put(uid,
                    SessionAndShadow.builder()
                            .session(getSession(uid, true))
                            .isOnline(true)
                            .shadow(createShadow(uid))
                            .build());
        }
    }

    @Override
    public void deviceOffline(String uid) {
        storage.get(uid).setIsOnline(false);
    }

    @Override
    public void reloadSession(String uid) {
        SessionAndShadow ss = storage.get(uid);
        if (ObjectUtil.isNotNull(ss)){
            ss.setSession(getSession(uid, true));
        }
    }

    @Override
    public void sendData(String uid, DownPayload payload) {
        throw new RuntimeException("短连接不支持主动推送消息");
    }

    @Override
    public DeviceShadow getShadow(String uid) {
        return storage.get(uid).getShadow();
    }

    @Override
    public void updateShadow(String uid, DeviceShadow shadow) {
        storage.get(uid).setShadow(shadow);
    }

    @Override
    public DeviceShadow createShadow(String uid) {
        return new DeviceShadow();
    }

    @Override
    public void deleteShadow(String uid) {

    }

    @Override
    public void updateShadowActual(String uid, Map<String, PointValue> actual) {
        getShadow(uid).upActual(uid, actual);
    }

    @Override
    public void updateShadowDesired(String uid, Map<String, PointValue> actual) {
        getShadow(uid).upDesired(actual);
    }

    @Override
    public DeviceSession getSession(String uid, boolean isNew) {
        SessionAndShadow sessionAndShadow = storage.get(uid);
        if (isNew || ObjectUtil.isNotNull(sessionAndShadow)
                || ObjectUtil.isNotNull(sessionAndShadow.getSession())){
            //todo 从数据库加载session
            DeviceMapper deviceMapper = BeanUtil.getBean(DeviceMapper.class);
            DeviceSessionDo sessionDo = deviceMapper.getSession(uid);
            DeviceSession deviceSession = DeviceSession.builder().tid(sessionDo.getTid())
                    .uid(sessionDo.getUid())
                    .secret(sessionDo.getSecret())
                    .deviceType(sessionDo.getDeviceType())
                    .protocolsType(sessionDo.getProtocolsType())
                    .model(JSONObject.parseObject(sessionDo.getModel(), DeviceModel.class))
                    .scoped(BeanUtil.getBean(DeviceShareMapper.class).getUserDeviceScoped(uid))
                    .build();
            sessionAndShadow.setSession(deviceSession);
        }
        return sessionAndShadow.getSession();
    }

    public DeviceSession getSession(String uid){
        return getSession(uid, false);
    }

    public byte[] encode(String uid, DownPayload payload) {
        return encode(getSession(uid).getTid(), payload);
    }

    public UpPayload decode(String uid, byte[] payload) {
        return decode(getSession(uid).getTid(), payload);
    }

    /**
     * 脚本处理数据到字节编码
     * @param tid 模板id
     * @param payload 数据
     * @return 字节
     */
    public byte[] encode(Long tid, DownPayload payload) {
        ScriptCodec scriptCodec = codecs.get(tid);
        if (ObjectUtil.isNotNull(scriptCodec)){
            return scriptCodec.encode(payload);
        }
        return payload.serialization();
    }

    /**
     * 脚本处理数据解码
     * @param tid 模板id
     * @param payload 数据
     * @return 解码后的数据
     */
    public UpPayload decode(Long tid, byte[] payload) {
        ScriptCodec scriptCodec = codecs.get(tid);
        if (ObjectUtil.isNotNull(scriptCodec)){
            return scriptCodec.decode(payload);
        }
        return JSONObject.parseObject(new String(payload), UpPayload.class);
    }

}
